/*
 * JaCoLiB
 * Copyright (C) 2008 Alessandro Serra
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package it.gashale.jacolib.main;

import java.util.HashMap;
import java.util.Iterator;

import it.gashale.jacolib.connection.Connection;
import it.gashale.jacolib.connection.auth.Authenticator;
import it.gashale.jacolib.connection.connector.ClientConnector;
import it.gashale.jacolib.connection.connector.ConnectionHandler;
import it.gashale.jacolib.connection.connector.ServerConnector;
import it.gashale.jacolib.core.JacolibError;
import it.gashale.jacolib.json.Serializer;
import it.gashale.jacolib.main.configurator.Environment;
import it.gashale.jacolib.main.configurator.SimplePropertiesEnvironment;
import it.gashale.jacolib.main.configurator.scripts.JacolibConfigurator;
import it.gashale.jacolib.process.cl.CLProcess;
import it.gashale.jacolib.rpc.QuitHandler;
import it.gashale.jacolib.rpc_connection.RPCConnection;
import it.gashale.jacolib.shell.RemoteShell;
import it.gashale.jacolib.shell.ShellChannel;
import it.gashale.jacolib.shell.ShellInterface;
import it.gashale.jacolib.shell.js.JSShell;

public class Jacolib {
	final private String prop_file_name="jacolib.properties";
	private ShellInterface shell;
	private Serializer serializer;
	private Environment env;
	
	private HashMap<String,RPCConnection> remote_shells;
	
	public Jacolib() throws JacolibError {
		shell=new JSShell();
		serializer=new Serializer();
		env=new SimplePropertiesEnvironment(prop_file_name);
		remote_shells=new HashMap<String,RPCConnection>();
		configure();
	}
	public ShellInterface getShell() {
		return shell;
	}
	public RemoteShell getRemoteShell(String name) {
		RPCConnection connection=remote_shells.get(name);
		RemoteShell rs=null;
		if(connection!=null) {
			rs=(RemoteShell)connection.getRPCChannel().getRPCClient();
		} 
		return rs;
	}
	/*
	 * configuration
	 */
	public void configure(String conf) throws JacolibError {
		String[] prefix=new String[1];
		prefix[0]="jacolib";
		JacolibConfigurator configurator=new JacolibConfigurator(env);
		configurator.setPrefixes(prefix);
		configurator.configure(this,conf);
	}
	public void configure() throws JacolibError {
		configure(null);
	}
	public void register(String name, RPCConnection rpconnection) {
		remote_shells.put(name,rpconnection);
	}
	public void unregister(String name) {
		remote_shells.remove(name);
	}
	/*
	 * 
	 */
	protected RPCConnection newShellChannel(final String name, Connection connection) throws JacolibError {
		ShellChannel channel=new ShellChannel(serializer,shell);
		RPCConnection rpcconnection=new RPCConnection(connection,channel);
		channel.setQuitPHandler(new QuitHandler() {
			public void handles() throws JacolibError {
				unregister(name);
			}
		});
		register(name,rpcconnection);
		return rpcconnection;
	}
	/*
	 * connection
	 */
	public RemoteShell connect(String name,String host, int port, String secret) throws JacolibError {
		ClientConnector connector=new ClientConnector(host,port,secret,new ConnectionHandler() {
			public void handle(Connection connection, int i) {
			}
		});
		Connection connection=connector.connect();
		return (RemoteShell)newShellChannel(name,connection).getRPCChannel().getRPCClient();
	}
	
	public RemoteShell connect_to_process(String name, CLProcess process) throws JacolibError {
		return connect_to_process(name, 40001, Authenticator.generateRandomString(), process);
	}
	
	public RemoteShell connect_to_process(final String name, int port, String secrete, CLProcess process) throws JacolibError {
		final ServerConnector server=new ServerConnector(port,secrete,null);
		ProcessConnectionHandler ch=new ProcessConnectionHandler() {
			public void handle(Connection connection, int i) throws JacolibError {
				rpc_connection=newShellChannel(name,connection);
				server.quit();
			}
		};
		server.setConnectionHandler(ch);
		process.setPort(port);
		process.setSecretString(secrete);
		process.start();
		server.run();
		return (RemoteShell)ch.rpc_connection.getRPCChannel().getRPCClient();
	}
	
	public void start_server(final String name, int port, String secret, boolean ump) throws JacolibError {
		ServerConnector connector=new ServerConnector(port,secret,new ConnectionHandler() {
			public void handle(Connection connection, int i) {
				try {
					String n=name+i;
					RPCConnection rpcconnection=newShellChannel(n,connection);
					rpcconnection.loop_until_quit();
				} catch (JacolibError e) {
					e.printStackTrace();
				}
			}
		});
		connector.run();
	}
	public void shutdown() {
		Iterator<RPCConnection> it=remote_shells.values().iterator();
		while(it.hasNext()) {
			try {
				it.next().quit();
			} catch (JacolibError e) {
				e.printStackTrace();
			}
		}
		remote_shells=null;
		shell=null;
		serializer=null;
		env=null;
	}
	
	
	abstract class ProcessConnectionHandler extends ConnectionHandler {
		public RPCConnection rpc_connection;
	};
}
